use crate::{
    fs::{DiskInodePosition, FileSystem}, 
    layout::{DirectoryEntry, DiskInode, DiskInodeKind, DIRENT_SIZE}, 
};

use std::{
    cell::RefCell,
    rc::Rc
};

#[derive(Clone)]
pub struct Inode {
    pub disk_inode_pos: DiskInodePosition,
    pub fs: Rc<RefCell<FileSystem>>,
}

impl Inode {
    pub fn new(
        disk_inode_pos: DiskInodePosition,
        fs: Rc<RefCell<FileSystem>>
    ) -> Self {
        Self { disk_inode_pos, fs }
    }

    pub fn is_file(&self) -> bool {
        self.read_disk_inode().kind == DiskInodeKind::File
    }

    pub fn is_directory(&self) -> bool {
        self.read_disk_inode().kind == DiskInodeKind::Directory
    }

    pub fn find(&self, name: &str) -> Option<Inode> {
        let disk_inode = self.read_disk_inode();
        if disk_inode.kind == DiskInodeKind::Directory {
            for dir_entry in self.dir_entries() {
                if dir_entry.name() == name {
                    let disk_inode_pos = self.fs.borrow().disk_inode_id_to_position(dir_entry.disk_inode_id() as usize);
                    return Some(
                        Inode::new(disk_inode_pos, self.fs.clone())
                    );
                } 
            }
        }

        None
    }

    pub fn create_file(&self, name: &str) -> Option<Inode> {
        self.create(name, DiskInodeKind::File)
    }

    pub fn create_directory(&self, name: &str) -> Option<Inode> {
        self.create(name, DiskInodeKind::Directory)
    }

    pub fn delete(&self) {
        if self.is_root() {
            return;
        }

        let mut rm_disk_inode = self.read_disk_inode();
        let rm_name = self.name();

        match rm_disk_inode.kind {
            DiskInodeKind::File => {
                let parent_inode = self.parent_inode().unwrap();
                let mut parent_disk_inode = parent_inode.read_disk_inode();
                let dir_entries = parent_inode.dir_entries();
                let mut rm_index = 0;
                let mut rm_inode_id = 0;
                for (index, dir_entry) in dir_entries.iter().enumerate() {
                    if dir_entry.name() == rm_name {
                        rm_index = index;
                        rm_inode_id = dir_entry.disk_inode_id() as usize;
                        break;
                    }
                }

                // Copy dir_entries[rm_index+1..] to dir_entries[rm_index]
                for i in (rm_index + 1)..dir_entries.len() {
                    // Copy dir_entries[i] to dir_entries[i - 1]
                    parent_disk_inode.write(DIRENT_SIZE * (i - 1), &dir_entries[i], &mut self.fs.borrow_mut());
                }
                parent_disk_inode.shrink_size((dir_entries.len() - DIRENT_SIZE) as u32, &mut self.fs.borrow_mut());

                // Write back
                parent_inode.write_disk_inode(&parent_disk_inode);

                rm_disk_inode.clear(&mut self.fs.borrow_mut());
                self.fs.borrow_mut().dealloc_inode_id(rm_inode_id);
            }
            DiskInodeKind::Directory => {
                
            }
        }
    }

    pub fn ls(&self) -> Option<Vec<String>> {
        let disk_inode = self.read_disk_inode();
        if disk_inode.kind != DiskInodeKind::Directory {
            return None;
        }

        Some(
            self.dir_entries().into_iter().map(|dir| 
                dir.name().into()
            ).collect()
        )
    }

    pub fn name(&self) -> String {
        let self_disk_inode_id = self.disk_inode_id();
        if self_disk_inode_id == 0 {
            return "/".to_string();
        }

        let parent_inode = self.parent_inode().unwrap();

        for dir_entry in parent_inode.dir_entries() {
            if dir_entry.disk_inode_id() as usize == self_disk_inode_id {
                return dir_entry.name().to_string();
            }
        }

        unreachable!("");
    }

    pub fn is_root(&self) -> bool {
        self.disk_inode_id() == 0
    }

    pub fn parent_inode(&self) -> Option<Inode> {
        if self.is_root() {
            None
        } else {
            self.find("..")
        }
    }

    pub fn read(&self, offset: usize, buf: &mut [u8]) -> usize {
        let disk_inode = self.read_disk_inode();
        if disk_inode.kind == DiskInodeKind::Directory {
            return 0;
        }
        if disk_inode.byte_size as usize <= offset {
            return 0;
        }

        disk_inode.read_bytes(offset, buf, &mut self.fs.borrow_mut())
    } 

    pub fn write(&self, offset: usize, buf: &[u8]) -> usize {
        let mut disk_inode = self.read_disk_inode();
        if disk_inode.kind == DiskInodeKind::Directory {
            return 0;
        }
        
        // No much space
        if buf.len() + offset > disk_inode.byte_size as usize {
            let new_byte_size = buf.len() + offset;
            disk_inode.increase_size(new_byte_size as u32, &mut self.fs.borrow_mut());
            self.write_disk_inode(&disk_inode);
        }

        disk_inode.write_bytes(offset, buf, &mut self.fs.borrow_mut())
    }
}

impl Inode {
    fn disk_inode_id(&self) -> usize {
        self.fs.borrow().disk_position_to_inode_id(&self.disk_inode_pos)
    }

    fn read_disk_inode(&self) -> DiskInode {
        *self.fs.borrow_mut().device.get_ref(
            self.disk_inode_pos.block_id, 
            self.disk_inode_pos.offset
        )
    }

    fn write_disk_inode(&self, disk_inode: &DiskInode) {
        *self.fs.borrow_mut().device.get_mut::<DiskInode>(
            self.disk_inode_pos.block_id, 
            self.disk_inode_pos.offset
        ) = *disk_inode;
    }

    fn dir_entries(&self) -> Vec<DirectoryEntry> {
        let disk_inode = self.read_disk_inode();
        let file_size = disk_inode.byte_size as usize / DIRENT_SIZE;
        (0..file_size).map(|file_index| -> DirectoryEntry {
            disk_inode.read(
                file_index * DIRENT_SIZE,
                &mut self.fs.borrow_mut(),
            )
        }).collect()
    }

    fn create(&self, name: &str, kind: DiskInodeKind) -> Option<Inode> {
        let mut disk_inode = self.read_disk_inode();
        if disk_inode.kind != DiskInodeKind::Directory {
            return None;
        }

        // inode already exit
        if self.dir_entries().into_iter().any(|dir_entry| {
            dir_entry.name() == name
        }) {
            return None;
        }

        // Increase disk inode size, for a new DirectoryEntry
        let file_size = disk_inode.byte_size as usize;
        disk_inode.increase_size((file_size + DIRENT_SIZE) as u32, &mut self.fs.borrow_mut());

        // Alloc a new disk inode
        let new_disk_inode_id = self.fs.borrow_mut().alloc_inode_id();
        let new_disk_inode_pos = self.fs.borrow().disk_inode_id_to_position(new_disk_inode_id);
        
        let mut new_disk_inode = DiskInode::new(kind);
        if kind == DiskInodeKind::Directory {
            // Add . .. to Directory
            new_disk_inode.increase_size((2 * DIRENT_SIZE) as u32, &mut self.fs.borrow_mut());

            // "."
            let self_entry = DirectoryEntry::new(".", new_disk_inode_id as u32);
            new_disk_inode.write(0, &self_entry, &mut self.fs.borrow_mut());

            // ".."
            let self_disk_inode_id = self.disk_inode_id();
            let parent_entry = DirectoryEntry::new("..", self_disk_inode_id as u32);
            new_disk_inode.write(DIRENT_SIZE, &parent_entry, &mut self.fs.borrow_mut());
        }

        let new_inode = Self::new(new_disk_inode_pos, self.fs.clone());
        new_inode.write_disk_inode(&new_disk_inode);

        // Crease new DirectoryEntry
        let dir_entry = DirectoryEntry::new(name, new_disk_inode_id as u32);

        // Write new DirectoryEntry back to disk inode(not the new one)
        disk_inode.write(file_size, &dir_entry, &mut self.fs.borrow_mut());

        // Write back 'disk_inode'
        self.write_disk_inode(&disk_inode);

        // Return Inode point to new disk inode
        Some(new_inode)
    }
}